<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>检查原型：更深入的研究</title>
  </head>
  <body>
    <script>
      // 在 JavaScript 中，函数可以拥有属性。所有函数都有一个名为 prototype 的特殊属性
      function doSomething() {}
      doSomething.prototype.foo = "bar";
      console.log(doSomething.prototype);
      // 你如何声明函数并不重要；
      // JavaScript 中的函数总有一个默认的
      // 原型属性——有一个例外：
      // 箭头函数没有默认的原型属性：
      const doSomethingFromArrowFunction = () => {};
      console.log(doSomethingFromArrowFunction.prototype);

      const doSomeInstancing = new doSomething();
      doSomeInstancing.prop = "some value"; // 向该对象添加一个属性
      console.log(doSomeInstancing);
      console.log("doSomeInstancing.prop:     ", doSomeInstancing.prop);
      console.log("doSomeInstancing.foo:      ", doSomeInstancing.foo);
      console.log("doSomething.prop:          ", doSomething.prop);
      console.log("doSomething.foo:           ", doSomething.foo);
      console.log("doSomething.prototype.prop:", doSomething.prototype.prop);
      console.log("doSomething.prototype.foo: ", doSomething.prototype.foo);
      /**
       * 然后，只有在查找完整个 [[Prototype]] 链之后，运行时才会断言该属性不存在，并得出该属性的值为 undefined。
       */

      // 使用不同的方法来创建对象和改变原型链
      // 1使用语法结构创建对象
      const o = { a: 1 };
      // 新创建的对象 o 以 Object.prototype 作为它的 [[Prototype]]
      // Object.prototype 的原型为 null。
      // o ---> Object.prototype ---> null

      const arr = ["yo", "whadup", "?"];
      // 数组继承了 Array.prototype（具有 indexOf、forEach 等方法）
      // 其原型链如下所示：
      // arr ---> Array.prototype ---> Object.prototype ---> null

      function f() {
        return 2;
      }
      // 函数继承了 Function.prototype（具有 call、bind 等方法）
      // f ---> Function.prototype ---> Object.prototype ---> null

      const p = { b: 2, __proto__: o };
      // 可以通过 __proto__ 字面量属性将新创建对象的
      // [[Prototype]] 指向另一个对象。
      // （不要与 Object.prototype.__proto__ 访问器混淆）
      // p ---> o ---> Object.prototype ---> null

      // 2使用构造函数
      function Graph() {
        this.vertices = [];
        this.edges = [];
      }

      Graph.prototype.addVertex = function (v) {
        this.vertices.push(v);
      };

      const g = new Graph();
      // g 是一个带有自有属性“vertices”和“edges”的对象。
      // 在执行 new Graph() 时，g.[[Prototype]] 是 Graph.prototype 的值。

      // 3使用 Object.create()
      const a = { a: 1 };
      // a ---> Object.prototype ---> null

      const b = Object.create(a);
      console.log(Object.getPrototypeOf(b) === a); // true
      // b ---> a ---> Object.prototype ---> null
      console.log(b.a); // 1 (inherited)

      const c = Object.create(b);
      console.log(c);
      // c ---> b ---> a ---> Object.prototype ---> null

      const d = Object.create(null);
      // d ---> null（d 是一个直接以 null 为原型的对象）
      console.log(d.hasOwnProperty);
      // undefined，因为 d 没有继承 Object.prototype

      // 4使用类

      // 5使用 Object.setPrototypeOf()
      const obj = { a: 1 };
      const anotherObj = { b: 2 };
      Object.setPrototypeOf(obj, anotherObj);
      // obj ---> anotherObj ---> Object.prototype ---> null

      // 性能
      // 原型链上较深层的属性的查找时间可能会对性能产生负面影响，这在性能至关重要的代码中可能会非常明显。
      // 此外，尝试访问不存在的属性始终会遍历整个原型链。
      // 在JavaScript中，对象的属性分为可枚举和不可枚举之分，它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。
    </script>
  </body>
</html>
